﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using NHibernate.Bytecode;
using NHibernate.Audit.Cfg;
using NHibernate.Event;
using NHibernate.Persister.Entity;

namespace NHibernate.Audit.Listener
{
    public class AuditEventListener : IPreInsertEventListener
    {

        private static readonly ILog log = log4net.LogManager.GetLogger(typeof(AuditEventListener));

        public AuditEventListener(AuditConfiguration auditConfiguration)
        {
            auditCfg = auditConfiguration;
        }

        private readonly AuditConfiguration auditCfg;

        public bool OnPreInsert(PreInsertEvent @event)
        {

            // Check entity name
            var entityName = @event.Persister.EntityName;
            if (auditCfg.ClassConvention.AuditClass(entityName))
            {
                try
                {
                    using (var childSession = CreateMapModeChildSession(@event.Session))
                    {
                        var revisionId = SaveRevision(childSession);
                        @event.Persister.SetIdentifier(@event.Entity, @event.Id, @event.Session.ActiveEntityMode);
                        SaveAuditEntity(childSession, revisionId, RevisionType.Add, @event);
                        childSession.Flush();
                    }
                }
                catch (Exception ex)
                {
                    log.Error(ex);
                    @event.Session.Transaction.Rollback();
                    throw;
                }
            }
            return false;
        }


        protected virtual ISession CreateMapModeChildSession(ISession session)
        {
            return session.GetSession(EntityMode.Map);
        }

        /// <summary>
        /// Saves the revision entity
        /// </summary>
        /// <param name="session">Child session with entityMode = map</param>
        /// <returns>Identity of the revision entity</returns>
        protected virtual object SaveRevision(ISession session)
        {
            var revCfg = auditCfg.RevisionConfiguration;
            var timestampPropertyName = revCfg.GetRevisionTimestampPropertyName();
            var map = new Dictionary<string, object>();
            map.Add(timestampPropertyName, DateTime.Now);
            return session.Save(revCfg.RevisionEntityName, map);
        }

        protected virtual void SaveAuditEntity(ISession session, object revisionId, RevisionType revisionType, AbstractPreDatabaseOperationEvent @event)
        {
            var realPersister = @event.Persister;
            var auditPersister = GetAuditPersisterFor(@event);
            var builder = new AuditEntityBuilder(realPersister, auditPersister, auditCfg);
            var auditEntityMap = builder.BuildAuditEntityMap(@event.Entity, @event.Session.ActiveEntityMode, revisionId);
            session.Save(auditPersister.EntityName, auditEntityMap);
        }

        protected virtual IEntityPersister GetAuditPersisterFor(AbstractPreDatabaseOperationEvent @event)
        {
            var realPersister = @event.Persister;
            var realEntityName = realPersister.EntityName;
            var auditEntityName = auditCfg.ClassConvention.GetAuditEntityNameFor(realEntityName);
            var auditEntity = @event.Session.Instantiate(auditEntityName, null);
            var auditPersister = @event.Session.GetEntityPersister(auditEntityName, auditEntity);
            return auditPersister;
        }

    }
}
